﻿// Copyright (c) 2010 Dan Popick

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

using Open.Web.Stijl.Configuration;

namespace StijlBilder.Configuration
{
    class ConfigUtility
    {
		static Open.Web.Stijl.Utility.Tracer<ConfigUtility> _log = new Open.Web.Stijl.Utility.Tracer<ConfigUtility>();

        class ArgPair
        {
            public string Name;
            public List<string> Parts;
            public string SimpleParts { get { return String.Join(" ", Parts.ToArray()); } }

            public ArgPair(string name) 
            { 
                Name = name;
                Parts = new List<string>();
            }
        }

		/// <summary>
		/// Extracts a config object from the command line. Also sets StijlConfiguration.Default
		/// </summary>
		/// <param name="args">Array of command line arguments</param>
		/// <param name="remainingArgs">Remaining arguments after all dashed args have been removed (e.g., -param val)</param>
		/// <returns>The configuration object (same as StijlConfiguration.Default)</returns>
		static public StijlConfiguration ExtractFromCommandLine(string[] args, out List<string> remainingArgs)
		{
			Dictionary<string, ArgPair> argList = new Dictionary<string, ArgPair>(StringComparer.InvariantCulture);
			remainingArgs = new List<string>();
			ArgPair currentPair = null;

			for (int i = 0; i < args.Length; ++i)
			{
				string arg = args[i];
				if (arg.StartsWith("-"))
				{
					string argName = arg.Substring(1);
					argList[argName] = currentPair = new ArgPair(argName);

					currentPair.Parts.Add(args[++i]);
				}
				else
					remainingArgs.Add(arg);
			}

			StijlConfiguration config = StijlConfiguration.Default;
			GetArgValue(argList, "a", () => config.Automatic);
			GetArgValue(argList, "sl", () => config.RepeatingSolutionLimit);
			GetArgValue(argList, "o", () => config.OutputDirectoryFormat);
			GetArgValue(argList, "tt", () => config.TiledImageThreshold);

			if (argList.Count > 0)
				throw new ArgumentException("The argument was not recognized.", argList.Keys.First());

			return config;
		}

		/// <summary>
		/// The upshot of using this method is that it does not affect the property when config value
		/// is not found. This allows us to set defaults for the config object in the constructor for
		/// that class and not worry about those values in this context.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="argList"></param>
		/// <param name="name"></param>
		/// <param name="expression"></param>
        static void GetArgValue<T>(Dictionary<String, ArgPair> argList, string name, Expression<Func<T>> expression)
        {
            ArgPair pair;
			if (!argList.TryGetValue(name, out pair))
				return;

			T value;
			try
			{
				value = (T)Convert.ChangeType(pair.SimpleParts, typeof(T));
			}
			catch (Exception ex)
			{
				_log.Error("Unable to convert value '{0}' to a {1}", pair.SimpleParts, typeof(T).Name);
				throw new ArgumentException(String.Format("Unable to parse the value '{0}'", pair.SimpleParts), ex);
			}

			if (!(expression is LambdaExpression) || !(expression.Body is MemberExpression))
			{
				_log.Error("Unable to convert value '{0}' to a {1}", pair.SimpleParts, typeof(T).Name);
				throw new ArgumentException("'expression' must be a single-member-access lambda expression.");
			}

			MemberExpression memberExp = expression.Body as MemberExpression;
			Type declaringType = memberExp.Member.DeclaringType;
			MemberInfo mi = declaringType.GetMember(memberExp.Member.Name).First();

			MemberExpression subExp = memberExp.Expression as MemberExpression;
			ConstantExpression constExp = subExp.Expression as ConstantExpression;
			var fieldInfo = constExp.Value.GetType().GetField(subExp.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			StijlConfiguration config = fieldInfo.GetValue(constExp.Value) as StijlConfiguration;

			_log.Debug("Read config value '{0}' for {1}", pair.SimpleParts, mi.Name);
			if (mi is PropertyInfo)
			{
				PropertyInfo pi = mi as PropertyInfo;
				pi.SetValue(config, value, null);
			}
			else if (mi is FieldInfo)
			{
				FieldInfo fi = mi as FieldInfo;
				fi.SetValue(config, value);
			}
			else
			{
				// this can only happen in development
				throw new ArgumentException("'expression' must be a field-or-property accessor lamba expression.");
			}
			
			argList.Remove(name);
        }
    }
}
